import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Block {
    public String prevHash; // 前一个区块的哈希
    public List<Transaction> transactions; // 存储在当前区块的交易记录(刚传入时进来的transactions仍是未收录的）
    public String hash; // 当前区块的哈希
    public int nonce;
    public Date time;

    /**
    * transactions：等待当前Block收录的交易记录池
    * preHash：上一个区块的哈希
    */
    public Block(List<Transaction> transactions, String prevHash) {
        this.transactions = transactions;
        this.prevHash = prevHash;
        this.hash = this.getBlockHash();
        this.nonce = 0; // nonce随机初始化即可
        this.time = new Date();
    }

    // 获取当前区块的哈希值
    public String getBlockHash() {
        // 直接将当前区块的所有成员变量化为字符串后计算哈希，作为当前区块的哈希  // 可行，但是就浪费的MerkleTree的树结构带来的快速验证的优势
        //return Utils.SHA256(transactions.toString(),prevHash, String.valueOf(nonce)); // 根据block的transactions、prevHash、nonce计算哈希值

        // 将当前区块的收录的交易transactions构造成MerkleTree，然后计算MerkleTree的根节点哈希作为当前区块的哈希
        if (transactions.size()==0){
            return Utils.SHA256(transactions.toString(),prevHash, String.valueOf(nonce));// 当前区块属于创世区块，此时链的交易池为空，所以无法构造出MerkleTree也无法计算出根哈希。只能用区块成员变量计算出哈希
        }
        return getMerkelTreeRootHash(transactions);
    }

    // 挖矿
    public void mine(int difficulty) throws Exception {
        if(!validateBlockTransactions()){
            System.out.println("Block transactions未通过验证，拒绝挖矿");
            return;
        }
        while(true){
            // 通过chain传入的difficulty设置哈希计算结果的比较位
            if(!hash.substring(0,difficulty).equals(getPreAnswer(difficulty))){
                nonce++;
                hash=getBlockHash();
            }else{
                break;
            }
        }
        System.out.println("挖矿结束。出块哈希为：" + hash);
    }

    // 在挖矿（挖哈希）前，应该先验证当前区块的所有交易记录的签名
    // （注意只有当被新建的区块被赋予挖哈希的结果赋值后，这个新建的区块才算被挖出（出块），所以在挖哈希前，需要验证传入的未收录的Tx的正确性）
    public boolean validateBlockTransactions() throws Exception {
        for(Transaction tx : transactions){
            // 验证当前交易的签名
            if(!tx.validate()){
                System.out.println("区块上存在某交易记录异常");
                return false;
            }
            // 验证当前交易在当前区块+链上其他所有区块中是否唯一（双花） todo：如何防止双花攻击？通过交易的哈希值？请回去再看一遍双花攻击的理论知识！

        }
        return true;
    }


    //  构造前几位为0的字符串，以供哈希计算结果进行比较
    public String getPreAnswer(int difficulty) {
        String answer = "";
        for(int i=0; i< difficulty;i++){
            answer+='0';
        }
        return answer;
    }

    /** 获取当前区块的交易列表transactions的MerkleTree的根哈希: getMerkelTreeRootHash()
     *
     *  如何将顺序的transaction组织成MerkleTree树形式：按照顺序读取List，按照深度优先构造Tree
     */
    public String getMerkelTreeRootHash(List<Transaction> transactions){
        List<String> transactionHashes = new ArrayList<>(); // 当前区块的所有交易的哈希
        for(Transaction tx : transactions){
            transactionHashes.add(tx.getTxHash());
        }
        // 构建MerkleTree(二叉树形式）
        while(transactionHashes.size()>1){
            List<String> tempHashes = new ArrayList<>(); // 相邻交易合并后用来更新transactionsHashes
            // 合并相邻交易的哈希
            for(int i=0; i<transactionHashes.size();i+=2){
                if(i+1 < transactionHashes.size()){
                    String combineHash = transactionHashes.get(i)+transactionHashes.get(i+1);
                    tempHashes.add(combineHash);
                }else{ // 当i+1 == transactions.size()，即奇数个交易中最后一个不做合并直接加入
                    tempHashes.add(transactionHashes.get(i));
                }
            }
            transactionHashes = tempHashes;
        }
        return transactionHashes.get(0);
    }

    @Override
    public String toString() {
        StringBuilder txSB = new StringBuilder();
        for (Transaction transaction : transactions) {
            txSB.append("                       ").append(transaction.toString()).append("\n");
        }
        return "Block{" +'\n' +
                "           prevHash='" + prevHash + '\''+'\n' +
                "           transactions[" +'\n' + txSB.toString() +
                "                       ]" + '\n' +
                "           hash='" + hash + '\''+'\n' +
                "           nonce=" + nonce + '\n' +
                "           time=" + time + '\n' +
                "           }";
    }
}
